#pragma once

#include "../Common.h"
#include <float.h>

namespace DirectX
{
  class ICollidable
  {
  protected:
    AABBDesc* boundingBox;

    ICollidable() : boundingBox(0)
    {
    }

  public:
    inline virtual AABBDesc* GetAABB() { return boundingBox;}

    inline void ComputeAABB(MeshDesc* meshData, XMFLOAT4X4 transform)
    {
      if(meshData == 0)
      {
        delete boundingBox;
        boundingBox = 0;
        return;
      }

      if(meshData->Indices == 0 || meshData->Vertices == 0 || meshData->NumIndices == 0 || meshData->NumVertices == 0)
      {
        delete boundingBox;
        boundingBox = 0;
        return;
      }

      XMVECTOR min, max;

      min = max = XMLoadFloat3(&meshData->Vertices[meshData->Indices[0]].pos);

      XMMATRIX matrix = XMLoadFloat4x4(&transform);

      for(int i = 0; i < meshData->NumIndices; ++i)
      {
        XMVECTOR tmp = XMLoadFloat3(&meshData->Vertices[meshData->Indices[i]].pos);
        min = XMVectorMin(min, tmp);
        max =  XMVectorMax(max, tmp);
      }    
      if(boundingBox == 0)
        boundingBox = new AABBDesc();

      XMVECTOR center = (min + max) * 0.5f;
      XMVECTOR extents = (max - min) * 0.5f;

      center = XMVector3Transform(center, matrix);

      XMStoreFloat3(&boundingBox->Center, center);
      XMStoreFloat3(&boundingBox->Extents, extents);

    }

    inline bool RayAABBCollision(XMFLOAT3 rayOrigin, XMFLOAT3 rayDirection)
    {


      static const XMVECTOR Epsilon =
      {
        1e-20f, 1e-20f, 1e-20f, 1e-20f
      };
      static const XMVECTOR FltMin =
      {
        -FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX
      };
      static const XMVECTOR FltMax =
      {
        FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX
      };

      XMVECTOR Origin = XMLoadFloat3(&rayOrigin);
      XMVECTOR Direction = XMLoadFloat3(&rayDirection);

      // Load the box.
      XMVECTOR Center = XMLoadFloat3( &boundingBox->Center );
      XMVECTOR Extents = XMLoadFloat3( &boundingBox->Extents );

      // Adjust ray origin to be relative to center of the box.
      XMVECTOR TOrigin = Center - Origin;

      // Compute the dot product againt each axis of the box.
      // Since the axii are (1,0,0), (0,1,0), (0,0,1) no computation is necessary.
      XMVECTOR AxisDotOrigin = TOrigin;
      XMVECTOR AxisDotDirection = Direction;

      // if (fabs(AxisDotDirection) <= Epsilon) the ray is nearly parallel to the slab.
      XMVECTOR IsParallel = XMVectorLessOrEqual( XMVectorAbs( AxisDotDirection ), Epsilon );

      // Test against all three axii simultaneously.
      XMVECTOR InverseAxisDotDirection = XMVectorReciprocal( AxisDotDirection );
      XMVECTOR t1 = ( AxisDotOrigin - Extents ) * InverseAxisDotDirection;
      XMVECTOR t2 = ( AxisDotOrigin + Extents ) * InverseAxisDotDirection;

      // Compute the max of min(t1,t2) and the min of max(t1,t2) ensuring we don't
      // use the results from any directions parallel to the slab.
      XMVECTOR t_min = XMVectorSelect( XMVectorMin( t1, t2 ), FltMin, IsParallel );
      XMVECTOR t_max = XMVectorSelect( XMVectorMax( t1, t2 ), FltMax, IsParallel );

      // t_min.x = maximum( t_min.x, t_min.y, t_min.z );
      // t_max.x = minimum( t_max.x, t_max.y, t_max.z );
      t_min = XMVectorMax( t_min, XMVectorSplatY( t_min ) );  // x = max(x,y)
      t_min = XMVectorMax( t_min, XMVectorSplatZ( t_min ) );  // x = max(max(x,y),z)
      t_max = XMVectorMin( t_max, XMVectorSplatY( t_max ) );  // x = min(x,y)
      t_max = XMVectorMin( t_max, XMVectorSplatZ( t_max ) );  // x = min(min(x,y),z)

      // if ( t_min > t_max ) return FALSE;
      XMVECTOR NoIntersection = XMVectorGreater( XMVectorSplatX( t_min ), XMVectorSplatX( t_max ) );

      // if ( t_max < 0.0f ) return FALSE;
      NoIntersection = XMVectorOrInt( NoIntersection, XMVectorLess( XMVectorSplatX( t_max ), XMVectorZero() ) );

      // if (IsParallel && (-Extents > AxisDotOrigin || Extents < AxisDotOrigin)) return FALSE;
      XMVECTOR ParallelOverlap = XMVectorInBounds( AxisDotOrigin, Extents );
      NoIntersection = XMVectorOrInt( NoIntersection, XMVectorAndCInt( IsParallel, ParallelOverlap ) );

      if( !XMVector3AnyTrue( NoIntersection ) )
      {
        // Store the x-component to *pDist
        //XMStoreFloat( pDist, t_min );
        return TRUE;
      }

      return FALSE;
    }

  private:
    inline bool XMVector3AnyTrue(FXMVECTOR V)
    {
      XMVECTOR C;
      C = XMVectorSwizzle(V,0, 1, 2, 0);

      return XMComparisonAnyTrue(XMVector4EqualIntR( C, XMVectorTrueInt() ));
    }
  };
}